#ifndef __M_HELPER_H__
    #define __M_HELPER_H__
#include <iostream> 
#include <fstream>
#include <string>
#include <vector>
#include <random>
#include <sstream>
#include <iomanip>
#include <atomic>
#include <sqlite3.h>
#include <cstring>  
#include <cerrno>
#include <sys/stat.h>
#include "mq_loger.hpp"

namespace mqhelper{

    class UUIDHelper{
        public:
        static std::string uuid(){
            std::random_device rd;
            std::mt19937_64 generator(rd());//rd()仿函数生成随机数种子
            std::uniform_int_distribution<int> distri(0,255);//uniform_int_distribution是一个限制类
            std::stringstream ss;
            //使用str打印
            for(int i = 0; i < 8 ;i++){//循环8次生成前8byte
                ss << std::setw(2) << std::setfill('0') << std::hex << distri(generator);
                if(i == 3 || i == 5 || i == 7){
                    ss << "-";
                }
                //将生成的随机数丢给distri对象的仿函数中进行限制大小
            }
            //实现后8byte的序号
            static std::atomic<size_t> seq(1);//初始值为1
            //静态原子的保证每次 进来 在之前序号的基础上继续
            size_t num = seq.fetch_add(1);//设置 自增长
            //同样事在num上操作
            //生成后面的 4 - 12，生成byte，16位十六进制
            // for(int i = 0;i < 8;i++){
            //     ss << std::setw(2) << std::setfill('0') << std::hex << ((num >> (i*8)) & (0xff));
            //     //其中 (num >> (i*8) & (0xff)) 的作用是：
            //     //首先：我们需要将num值每次取出8位，也就是1byte，也就是2位16进制（4位 = 1十六进制），所以每次移动8byte，其中我们拿第一个举例：移动i == 0 的 时候就是取前8为，当为1当时后，先右移1 * 8位，就能取到后面9 ~ 16
            //     // 然后 需要 按位与& 0xff，这里好理解，通过按位与操作，只取前8位
            //     if(i == 1 ){//分成 4 - 12
            //         ss << "-";
            //     }
            // }
            //上述写法虽然也ok，但是有点反人性（得到的序号是反过来的）不好看，解决也很简单我们就将循环遍历翻过来就哈
            for(int i = 7;i >= 0;i--){
                ss << std::setw(2) << std::setfill('0') << std::hex << ((num >> (i*8)) & (0xff));
                //其中 (num >> (i*8) & (0xff)) 的作用是：
                //首先：我们需要将num值每次取出8位，也就是1byte，也就是2位16进制（4位 = 1十六进制），所以每次移动8byte，其中我们拿第一个举例：移动i == 0 的 时候就是取前8为，当为1当时后，先右移1 * 8位，就能取到后面9 ~ 16
                // 然后 需要 按位与& 0xff，这里好理解，通过按位与操作，只取8位
                if(i == 6){//分成 4 - 12
                    ss << "-";
                }
            }
            return ss.str();
        }

    };

    class StrHelper{
        public:
            size_t split(const std::string& str,const std::string& sep,std::vector<std::string>& result){
            //将str 通过 sep 分隔
            //将结果全部存放到 result
            int pos = 0 , idx = 0;//pos表示要分割的字符串的首位置,idx表示分隔符的下标
            while(idx <= str.size()){
                idx = str.find(sep,pos);
                if(idx == std::string::npos){
                    //如果找不到就表示不需要分隔
                    std::string tmp_str = str.substr(pos);

                    result.push_back(tmp_str);//不写len，直接将所有都获取了
                    return result.size();
                }
                //需要将出现连续分隔符的情况进行 特殊处理： news....music.#.pop 此处就 . 为分割符 就会出现连续的问题，而出现连续的时候，会截取到空字符串，就没必要

                if(pos == idx)//当出现连续时 pos 会等于 idx 
                {
                    pos = idx + sep.length();//注意也需要往前走哈
                    continue; 
                }
                std::string tmp_str = str.substr(pos,idx - pos);
                result.push_back(tmp_str);//len = idx - pos
                pos = idx + sep.length();//
            }
            return result.size();
        }
    };




    class  SqliteHelper{
    typedef int (*SqliteCallback)(void*,int,char**,char**);
    // 1. void* 是外部传递进来的参数（通过强转使用）
    // 2. int 是一行中的数据列数
    // 3. 第一个char** 是储存每一行的字符指针数组
    // 4. 第二个char** 是存储每一列的字段名称
    
    public:
        SqliteHelper(const std::string& dbfile):_dbfile(dbfile),_handler(nullptr){}


    bool open(int safe_leve = SQLITE_OPEN_FULLMUTEX)//SQLITE_OPEN_FULLMUTEX默认就为串行化模式
    {
        //int sqlite3_open_v2(const char *filename, sqlite3 **ppDb, int flags, const char *zVfs );
        int ret = sqlite3_open_v2(_dbfile.c_str(),&_handler,safe_leve | SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE,nullptr);
        //第一个参数是db文件路径
        //第二个参数是一个输出型变量，我们将sqlite的句柄传进去，获取open后db的句柄
        //第三个参数是文件打开的默认权限和方式 和 线程的安全等级
        //SQLITE_OPEN_CREATE： 不存在数据库⽂件则创建
        // SQLITE_OPEN_READWRITE -- 以可读可写⽅式打开数据库⽂件
        //第四个参数设为空即可
        if(ret != SQLITE_OK)
        {
            ELOG( "创建/打开sqlite数据库失败：%s",sqlite3_errmsg(_handler));
            return false;
        }
        else{
            DLOG("打开数据库成功");
            return true;
        }
    }

    //提前设置 SqliteCallback ==  int (*SqliteCallback)(void*,int,char**,char**)回调函数
    bool exec(const std::string& sql,SqliteCallback cb,void* arg){
        // DLOG("sql：%s -------",sql.c_str())
        int ret = sqlite3_exec(_handler,sql.c_str(),cb,arg,nullptr);
        //
        if(ret != SQLITE_OK)
        {
            std::cout << sql << std::endl;
            ELOG( "执行失败、查看错误信息: %s",sqlite3_errmsg(_handler));
            return false;
        }
        else{
            // DLOG ("执行成功");
            return true;
        }
    }

    bool close(){
        sqlite3_close_v2(_handler);//
        return true;
    }

    private:
        std::string _dbfile;
        sqlite3 * _handler;
    };

    class FileHelper{
        public:
            FileHelper(const std::string& filename):_filename(filename){
            }
            /**
             * 判断路径是否存在
             */
            bool exists(){
                //使用stat函数
                struct stat st;
                return (stat(_filename.c_str(),&st) == 0);
            }
            /**
             * 判断文件大小，直接通过获取的文件状态返回的对象st
             * 获取其内部成员 st_size 即是文件的大小
             */
            size_t sizeFile(){
                struct stat st;
                stat(_filename.c_str(),&st);
                return  st.st_size;
            }
            bool renameFile(const std::string& newfilename){
                //使用函数rename
                return (std::rename(_filename.c_str(),newfilename.c_str()) == 0);//返回0表示修改成功
            }
            static bool removeFile(const char* filename){
                return ::remove(filename) == 0;//返回0表示删除成功
            }

            static bool createFile(const char* filename){
                std::fstream fs(filename, std::ios::binary | std::ios::out);
                //binary二进制打开防止数据出现问题，
                //out：写权限
                return fs.is_open();//通过打开的文件判断是否打开成功
            }

            /**
             * 文件的读取
             * 将读取的文件中的所有内容放进buf中
             * buf：输出型参数，存要读取的数据
             * len : byte
             */
            bool readFile(std::string& buf,size_t offset,size_t len){
                //1. 打开文件,调整buf空间
                std::ifstream fs(_filename,std::ios::binary | std::ios::in);//二进制和读方式打开文件filename
                //调整buf空间
                buf.resize(len);

                    // 判断是否打开成功
                if(!fs.is_open()){
                    ELOG("%s,文件打开失败！",_filename.c_str());
                    return false;
                }

                //2. 从当前位置读取所有内容放到buf中
                    //2.1 偏移到文件的指定位置
                fs.seekg(offset,std::ios::beg);//从文件开始beg，偏移到offset

                    //2.2 读取所有内容给到buf
                fs.read(&buf[0],len);//
                
                if(fs.good() == false){//判断上一步读取是否成功
                    ELOG("%s,文件读取数据失败",_filename.c_str());
                    return false;
                }
                fs.close();
                return true;
            }
            bool readFile(std::string& buf){
                //获取文件大小
                size_t filesize = sizeFile();
                //调整body空间：resize
                buf.resize(filesize);
                return readFile(buf,0,filesize);
            }

            /**
             * 文件的写入
             * 将读取的文件中的所有内容放进buf中
             * body：所要写的内容
             * offset：偏移量
             * len：写入长度
             */
            bool writeFile(const std::string& body,size_t offset,size_t len){
                //1. 打开文件
                std::ofstream fs(_filename.c_str(),std::ios::binary | std::ios::out | std::ios::in);
                    // 判断是否打开成功
                if(!fs.is_open()){
                    ELOG("%s,文件打开失败！",_filename.c_str());
                    return false;
                }

                //2. 跳到指定位置写：
                fs.seekp(offset,std::ios::beg);

                //3. 写
                fs.write(body.c_str(),len);

                if(fs.good() == false){
                    ELOG("%s,文件写入数据失败",_filename.c_str());
                    return false;
                }
                //3. 关闭
                fs.close();
                return true;
            }
            bool writeFile(const std::string& body){
                return writeFile(body,0,body.size());        
            }

//重新修改writeFile、readFIle
            bool read(char *body, size_t offset, size_t len) {
            //1. 打开文件
            std::ifstream ifs(_filename, std::ios::binary | std::ios::in); 
            if (ifs.is_open() == false) {
                ELOG("%s 文件打开失败！", _filename.c_str());
                return false;
            }
            //2. 跳转文件读写位置
            ifs.seekg(offset, std::ios::beg);
            //3. 读取文件数据
            ifs.read(body, len);
            if (ifs.good() == false) {
                ELOG("%s 文件读取数据失败！！", _filename.c_str());
                ifs.close();
                return false;
            }
            //4. 关闭文件
            ifs.close();
            return true;
        }
        bool read(std::string &body) {
            //获取文件大小，根据文件大小调整body的空间
            size_t fsize = this->sizeFile();
            body.resize(fsize);
            return read(&body[0], 0, fsize);
        }
        bool write(const char *body, size_t offset, size_t len) {
            //1. 打开文件
            std::fstream fs(_filename, std::ios::binary | std::ios::in | std::ios::out); 
            if (fs.is_open() == false) {
                ELOG("%s 文件打开失败！", _filename.c_str());
                return false;
            }
            //2. 跳转到文件指定位置
            fs.seekp(offset, std::ios::beg);
            //3. 写入数据
            fs.write(body, len);
            if (fs.good() == false) {
                ELOG("%s 文件写入数据失败！！", _filename.c_str());
                fs.close();
                return false;
            }
            //4. 关闭文件
            fs.close();
            return true;
        }
        bool write(const std::string &body) {
            return write(body.c_str(), 0, body.size());
        }


            static bool createDirectory(const std::string& path){
                if(mqhelper::FileHelper(path).exists() == true){
                    return true;
                }
                //循环创建
                size_t pos = -1,idx = 0;
                //pos查找/位置

                pos = path.find_first_of("/");
                while(pos != std::string::npos)
                {
                    std::string subpath = path.substr(0,pos);//每次从 0 ~ pos（因为要保证层级结构）
                    int ret = mkdir(subpath.c_str(),0775);//创建目录 ，并设置 权限为 775 
                    if(ret != 0 && errno != EEXIST){
                        ELOG("创建目录 %s 失败",subpath.c_str());
                        return false;
                    }
                    idx = pos + 1;//idx记录未获取的目录的 / 分割的字符串起始位置
                    pos = path.find_first_of('/',idx);
                }
                //当出来时，就表示找不到 "/"
                //也就表示 前面的目录 即使不存在也会被创建好了
                if( mkdir(path.c_str(),0775) != 0){
                    if(errno == EEXIST){
                        return true;
                    }
                    return false;
                }
                return true;
            }

            static bool removeDirectory(const std::string& path){
                //此处使用stytem来执行 rm -rf 指令进行删除操作，这样就能减代码量（就不在需要再去写删除多层目录的代码）
                std::string cmd = "rm -rf " + path;
                return system(cmd.c_str()) != -1;
            }

            static std::string parentDirectory(const std::string& path){
                //获取父目录
                //直接从后往前找到 / 前面就是父级目录
                int pos = path.find_last_of("/");
                if(pos == std::string::npos){
                    //表示当前路径就是最终目录，返回./
                    return "./";
                }
                return path.substr(0,pos);
            }

        private:
            std::string _filename;
    };
}

#endif
